package icmp

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"net"
	"time"
)

const (
	_ICMP_TIMEOUT   = 100
	_ICMP_DATA_SIZE = 32
)

// ICMP 定义 ICMP 协议包头
type ICMP struct {
	// Type 类型
	Type uint8
	// Code 代码
	Code uint8
	// Checksum 校验和
	Checksum uint16
	// Identifier 标识符
	Identifier uint16
	// SequenceNum 序列号
	SequenceNum uint16
}

// Ping 执行 ping 命令，不打印网络状态
func Ping(ip string, num int) (loss float64, err error) {
	// 创建 ICMP 连接
	conn, err := net.DialTimeout("ip:icmp", ip, time.Duration(_ICMP_TIMEOUT)*time.Millisecond)
	if err != nil {
		return 100.0, err
	}
	defer conn.Close()

	// 创建 ICMP 数据包
	icmp := ICMP{
		Type:        8,
		Identifier:  1,
		SequenceNum: 1,
	}

	// 写入 ICMP 数据到 buffer
	var buffer bytes.Buffer
	binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式写入（低位对应高地址）
	buffer.Write(make([]byte, _ICMP_DATA_SIZE))
	data := buffer.Bytes()

	var successTimes int        // 成功次数
	var failTimes int           // 失败次数
	var minTime = math.MaxInt32 // 单次最短时间
	var maxTime int             // 单次最长时间
	var totalTime int           // 累积执行时间

	for i := 0; i < num; i++ {
		icmp.SequenceNum = uint16(1)
		// 检验和设为0
		data[2] = byte(0)
		data[3] = byte(0)
		data[6] = byte(icmp.SequenceNum >> 8)
		data[7] = byte(icmp.SequenceNum)
		// 设置 checksum
		icmp.Checksum = checkICMPSum(data)
		data[2] = byte(icmp.Checksum >> 8)
		data[3] = byte(icmp.Checksum)

		// 开始时间
		t1 := time.Now()
		conn.SetDeadline(t1.Add(time.Duration(_ICMP_TIMEOUT) * time.Millisecond))
		// 设置 icmp 包 checksum 校验和
		if _, err := conn.Write(data); err != nil {
			return 100.0, err
		}

		buf := make([]byte, 65535)
		if _, err := conn.Read(buf); err != nil {
			failTimes++
			continue
		}

		//time.Now()转换为毫秒
		et := int(time.Since(t1) / 1000000)
		if minTime > et {
			minTime = et
		}
		if maxTime < et {
			maxTime = et
		}
		totalTime += et
		successTimes++

		time.Sleep(1 * time.Second)
	}

	return float64(failTimes) / float64(successTimes+failTimes), nil
}

// PingAndPrint 执行 ping 命令，并实时打印网络状态
func PingAndPrint(ip string, num int) (loss float64, err error) {
	// 创建 ICMP 连接
	conn, err := net.DialTimeout("ip:icmp", ip, time.Duration(_ICMP_TIMEOUT)*time.Millisecond)
	if err != nil {
		return 100.0, err
	}
	defer conn.Close()

	// 创建 ICMP 数据包
	icmp := ICMP{
		Type:        8,
		Identifier:  1,
		SequenceNum: 1,
	}

	// 写入 ICMP 数据到 buffer
	var buffer bytes.Buffer
	binary.Write(&buffer, binary.BigEndian, icmp) // 以大端模式写入（低位对应高地址）
	buffer.Write(make([]byte, _ICMP_DATA_SIZE))
	data := buffer.Bytes()

	var successTimes int        // 成功次数
	var failTimes int           // 失败次数
	var minTime = math.MaxInt32 // 单次最短时间
	var maxTime int             // 单次最长时间
	var totalTime int           // 累积执行时间

	fmt.Printf("\n正在 ping %s 具有 %d 字节的数据:\n", ip, _ICMP_DATA_SIZE)
	for i := 0; i < num; i++ {
		icmp.SequenceNum = uint16(1)
		// 检验和设为0
		data[2] = byte(0)
		data[3] = byte(0)
		data[6] = byte(icmp.SequenceNum >> 8)
		data[7] = byte(icmp.SequenceNum)
		// 设置 checksum
		icmp.Checksum = checkICMPSum(data)
		data[2] = byte(icmp.Checksum >> 8)
		data[3] = byte(icmp.Checksum)

		// 开始时间
		t1 := time.Now()
		conn.SetDeadline(t1.Add(time.Duration(_ICMP_TIMEOUT) * time.Millisecond))
		// 设置 icmp 包 checksum 校验和
		if _, err := conn.Write(data); err != nil {
			return 100.0, err
		}

		buf := make([]byte, 65535)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("请求超时。")
			failTimes++
			continue
		}

		//time.Now()转换为毫秒
		et := int(time.Since(t1) / 1000000)
		if minTime > et {
			minTime = et
		}
		if maxTime < et {
			maxTime = et
		}
		totalTime += et
		successTimes++

		fmt.Printf("来自 %s 的回复: 字节=%d 时间=%dms TTL=%d\n", ip, len(buf[28:n]), et, buf[8])
		time.Sleep(1 * time.Second)
	}

	fmt.Printf("\n%s 的 Ping 统计信息:\n", ip)
	fmt.Printf("    数据包: 已发送 = %d，已接收 = %d，丢失 = %d (%.2f%% 丢失)，\n", successTimes+failTimes, successTimes, failTimes, float64(failTimes*100)/float64(successTimes+failTimes))
	if maxTime != 0 && minTime != math.MaxInt32 {
		fmt.Printf("往返行程的估计时间(以毫秒为单位):\n")
		fmt.Printf("    最短 = %dms，最长 = %dms，平均 = %dms\n", minTime, maxTime, totalTime/successTimes)
	}

	return float64(failTimes) / float64(successTimes+failTimes), nil
}

// checkICMPSum 计算校验和
func checkICMPSum(data []byte) uint16 {
	var sum uint32
	var length = len(data)
	var index int

	for length > 1 { // 溢出部分直接去除
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length == 1 {
		sum += uint32(data[index])
	}
	sum = uint32(uint16(sum>>16) + uint16(sum))
	sum = uint32(uint16(sum>>16) + uint16(sum))
	return uint16(^sum)
}
